// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/logging.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/simple_thread.h"
#include "base/threading/thread_local.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base {

namespace {

    class ThreadLocalTesterBase : public base::DelegateSimpleThreadPool::Delegate {
    public:
        typedef base::ThreadLocalPointer<char> TLPType;

        ThreadLocalTesterBase(TLPType* tlp, base::WaitableEvent* done)
            : tlp_(tlp)
            , done_(done)
        {
        }
        ~ThreadLocalTesterBase() override { }

    protected:
        TLPType* tlp_;
        base::WaitableEvent* done_;
    };

    class SetThreadLocal : public ThreadLocalTesterBase {
    public:
        SetThreadLocal(TLPType* tlp, base::WaitableEvent* done)
            : ThreadLocalTesterBase(tlp, done)
            , val_(NULL)
        {
        }
        ~SetThreadLocal() override { }

        void set_value(char* val) { val_ = val; }

        void Run() override
        {
            DCHECK(!done_->IsSignaled());
            tlp_->Set(val_);
            done_->Signal();
        }

    private:
        char* val_;
    };

    class GetThreadLocal : public ThreadLocalTesterBase {
    public:
        GetThreadLocal(TLPType* tlp, base::WaitableEvent* done)
            : ThreadLocalTesterBase(tlp, done)
            , ptr_(NULL)
        {
        }
        ~GetThreadLocal() override { }

        void set_ptr(char** ptr) { ptr_ = ptr; }

        void Run() override
        {
            DCHECK(!done_->IsSignaled());
            *ptr_ = tlp_->Get();
            done_->Signal();
        }

    private:
        char** ptr_;
    };

} // namespace

// In this test, we start 2 threads which will access a ThreadLocalPointer.  We
// make sure the default is NULL, and the pointers are unique to the threads.
TEST(ThreadLocalTest, Pointer)
{
    base::DelegateSimpleThreadPool tp1("ThreadLocalTest tp1", 1);
    base::DelegateSimpleThreadPool tp2("ThreadLocalTest tp1", 1);
    tp1.Start();
    tp2.Start();

    base::ThreadLocalPointer<char> tlp;

    static char* const kBogusPointer = reinterpret_cast<char*>(0x1234);

    char* tls_val;
    base::WaitableEvent done(WaitableEvent::ResetPolicy::MANUAL,
        WaitableEvent::InitialState::NOT_SIGNALED);

    GetThreadLocal getter(&tlp, &done);
    getter.set_ptr(&tls_val);

    // Check that both threads defaulted to NULL.
    tls_val = kBogusPointer;
    done.Reset();
    tp1.AddWork(&getter);
    done.Wait();
    EXPECT_EQ(static_cast<char*>(NULL), tls_val);

    tls_val = kBogusPointer;
    done.Reset();
    tp2.AddWork(&getter);
    done.Wait();
    EXPECT_EQ(static_cast<char*>(NULL), tls_val);

    SetThreadLocal setter(&tlp, &done);
    setter.set_value(kBogusPointer);

    // Have thread 1 set their pointer value to kBogusPointer.
    done.Reset();
    tp1.AddWork(&setter);
    done.Wait();

    tls_val = NULL;
    done.Reset();
    tp1.AddWork(&getter);
    done.Wait();
    EXPECT_EQ(kBogusPointer, tls_val);

    // Make sure thread 2 is still NULL
    tls_val = kBogusPointer;
    done.Reset();
    tp2.AddWork(&getter);
    done.Wait();
    EXPECT_EQ(static_cast<char*>(NULL), tls_val);

    // Set thread 2 to kBogusPointer + 1.
    setter.set_value(kBogusPointer + 1);

    done.Reset();
    tp2.AddWork(&setter);
    done.Wait();

    tls_val = NULL;
    done.Reset();
    tp2.AddWork(&getter);
    done.Wait();
    EXPECT_EQ(kBogusPointer + 1, tls_val);

    // Make sure thread 1 is still kBogusPointer.
    tls_val = NULL;
    done.Reset();
    tp1.AddWork(&getter);
    done.Wait();
    EXPECT_EQ(kBogusPointer, tls_val);

    tp1.JoinAll();
    tp2.JoinAll();
}

TEST(ThreadLocalTest, Boolean)
{
    {
        base::ThreadLocalBoolean tlb;
        EXPECT_FALSE(tlb.Get());

        tlb.Set(false);
        EXPECT_FALSE(tlb.Get());

        tlb.Set(true);
        EXPECT_TRUE(tlb.Get());
    }

    // Our slot should have been freed, we're all reset.
    {
        base::ThreadLocalBoolean tlb;
        EXPECT_FALSE(tlb.Get());
    }
}

} // namespace base
